package sketch.generator;

import java.util.ArrayList;
import java.util.Collection;

import randoop.StatementKind;
import sketch.ExecutableSequenceContent;
import sketch.MethodExecOutcome;
import sketch.SMethod;
import sketch.SketchSequence;
import sketch.util.Checker;
import sketch.util.Randomness;
import sketch.util.RandoopUtil;
import sketch.util.SketchUtil;

/**
 * The behavior of random sequence generator is somehow like single sequence generation,
 * except that every round is non-deterministic
 * */
public class RandomSequenceGenerator<T extends SketchSequence> extends SequenceGenerator<T> {

	private SketchSequence randomCreatedSequence = null;
	
	private Collection<Object> allocated = new ArrayList<Object>();
	
	private String randomMethod = null;
	
	private Object currentObject = null;
	
	public RandomSequenceGenerator(Object receiver, SMethod smethod, ValueGenerator<? extends Object> generator) {
		super(receiver, smethod, generator);
		Checker.checkTrue(smethod.getMethodNum() > 1, "The number of method in SMethod object should be 1.");
		this.fillAllocatedCollection();
	}
	
	private void fillAllocatedCollection() {
		while(this.valueGenerator.hasNext()) {
			this.allocated.add(this.valueGenerator.next());
		}
		//should reset it
		this.valueGenerator.reset();
//		System.out.println("size of allocated: " + this.allocated.size());
//		System.exit(1);
	}
	
	@Override
	public SketchSequence current() {
		this.randomCreatedSequence = this.composeSequence();
		return this.randomCreatedSequence;
	}
	
	@Override
	protected SketchSequence generateNext() {
		if(!hasNext()) {
			throw new IterationException("Need to call reset() before generating next random sequence!");
		}
		this.randomCreatedSequence = this.composeSequence();
		return this.randomCreatedSequence;
	}
	
	@Override
	public void reset() {
		super.reset();
		this.randomCreatedSequence = null;
	}

	@Override
	public boolean hasNext() {
		return isReset();
	}
	
	@Override
	public ExecutableSequenceContent[] executables() {
		ExecutableSequenceContent[] contents = new ExecutableSequenceContent[1];
        Checker.checkNull(this.randomMethod, "The current random method could not be null!");
		
        StatementKind s = GenTests.lookupForStatement(this.smethod.sclazz.clazz, this.randomMethod);
		Checker.checkNull(s, "StatementKind for method: " + randomMethod + " is null!");
		
		boolean hasArgument = RandoopUtil.hasParam(s);
		ExecutableSequenceContent content = hasArgument ?
				new ExecutableSequenceContent(super.getReceiver(), s, this.currentObject) :
				    new ExecutableSequenceContent(super.getReceiver(), s);
		
		contents[0] = content;
		
		return contents;
	}
	
//	@Override
//	public Object[] evaluate() {
//		Object[] results = new Object[1];
//		Checker.checkNull(this.randomMethod, "The current random method could not be null!");
//		
//        StatementKind s = GenTests.lookupForStatement(this.smethod.sclazz.clazz, this.randomMethod);
//		Checker.checkNull(s, "StatementKind for method: " + randomMethod + " is null!");
//		
//		boolean hasArgument = RandoopUtil.hasParam(s);
//		ExecutableSequenceContent content = hasArgument ?
//				new ExecutableSequenceContent(super.getReceiver(), s, this.currentObject) :
//				    new ExecutableSequenceContent(super.getReceiver(), s);
//		
//		results[0] = content.excute().getReturnObject();
//		
//		return results;
//	}

	@Override
	public SketchSequence composeSequence() {
		this.randomMethod = this.smethod.randomMethod();
		
		if(!this.valueGenerator.hasNext()) {
			throw new IterationException("No data in the value generator in RandomSequenceGenerator");
		}
		
		StatementKind s = GenTests.lookupForStatement(this.smethod.sclazz.clazz, randomMethod);
		
		Checker.checkNull(s, "StatementKind for method: " + randomMethod + " is null!");
		
		this.currentObject = Randomness.randomSetMember(this.allocated);
		
		SketchSequence sequence = new SketchSequence();
		
		//creating a sketch sequence requires: 1) statement, 2) argument values
		//how to create for a primtive type declaration
		
		//transform to the text format
//		sequence.add("calling randomly: " + super.reciever.getClass()
//				+ ": " + randomMethod + " with args: " + obj);
		sequence.addText(SketchSequence.RECEIVER//SketchUtil.createVarName(super.getReceiver().getClass())
				+ "." + randomMethod + "(" +  currentObject + ");");
		
		boolean hasArgument = (RandoopUtil.getParamNum(s) != 0);
		//ReflectionExecutionOutcome execResult = null;
		ExecutableSequenceContent content = null;
		if(!hasArgument) {
		    //execResult = ReflectiveExecutions.reflectiveExecute(super.getReceiver(), s);
		    content = new ExecutableSequenceContent(super.getReceiver(), s);
		} else {
//			execResult = ReflectiveExecutions.reflectiveExecute(super.getReceiver(), s, obj);
			content = new ExecutableSequenceContent(super.getReceiver(), s, currentObject);
		}
		
		//System.out.println("The result of randomly executing: " + execResult + ", its receiver object id: " + super.getReceiverId());
//		sequence.addExecutableContent(content);
		return sequence;
	}

	@Override
	public SketchSequence getGeneratedSequence() {
		Checker.checkNull(this.randomCreatedSequence, "There is no sequence created randomly.");
		return this.randomCreatedSequence;
	}

//	@Override
//	protected MethodExecOutcome[] executionResult() {
//		throw new UnsupportedOperationException("The executionResult for random sequence generator is not supported!");
//	}

}
